import {withLock} from "easylock";
import {Types} from "mongoose"
import ms = require("ms");
import * as request from 'superagent'
import {InstanceType} from "typegoose"
import {ConsumeRecordModel} from "../../database/models/consumeRecord";
import {GameActivity, GameActivityModel} from "../../database/models/gameActivity";
import {GamePrize, GamePrizeModel} from "../../database/models/gamePrize";
import {LipstickSeries, LipstickSeriesModel} from "../../database/models/lipstickSeries";
import {GiftState, MailModel, MailState, MailType} from "../../database/models/mail";
import {Player, PlayerModel} from "../../database/models/player";
import {ProductModel, RMB_2_COIN_RATE} from "../../database/models/Product";
import {
  getProductsFromPressGroupLevels,
  PreProducts,
  PressGroup,
  PressGroupIncomeModel,
  PressGroupModel,
} from "../../database/models/ProductGroup";
import {ErrorCode, InternalError} from "../error";
import {CLIENT_TYPE, FANGKA_2_COIN, pcUrl, putDataInBroadcastInfo} from '../utils'
import {detectStockOfLips, playerUseCoinOrGemOrPoint} from "./lipStickGame";

export const PRESS_GAME_RESUME_TIME: number = 60
export const PRESS_GAME_TIME: number = 31
export const PRICE_2_INCOME: number = 2 * RMB_2_COIN_RATE

export async function pressGroupIncomeThreshold(pressGroupId) { // 大盘
  const pressGroupOnStock = {
    level1: null, price1: 0,
    level2: null, price2: 0,
    level3: null, price3: 0,
  }
  const pressGroupNow = await PressGroupModel.findById(pressGroupId)
    .populate(`level1`)
    .populate(`level2`)
    .populate(`level3`)
    .lean()
  if (!pressGroupNow) {
    return null
  }
  for (let i = 0; i < 3; i++) {
    for (const lipstickSeries of pressGroupNow[`level${i + 1}`]) {
      if (!lipstickSeries) {
        continue
      }
      if (lipstickSeries.onStock) {
        if (lipstickSeries.seriesPrice > pressGroupOnStock[`price${i + 1}`]) {
          pressGroupOnStock[`price${i + 1}`] = lipstickSeries.seriesPrice
          pressGroupOnStock[`level${i + 1}`] = lipstickSeries
        }
      }
    }
  }
  return {
    income0: 0,
    income1: pressGroupOnStock.price1 * PRICE_2_INCOME, level1: pressGroupOnStock.level1,
    income2: pressGroupOnStock.price2 * PRICE_2_INCOME, level2: pressGroupOnStock.level2,
    income3: pressGroupOnStock.price3 * PRICE_2_INCOME, level3: pressGroupOnStock.level3,
  }
}

export class PressGame {

  constructor(readonly player: InstanceType<Player>,
              readonly pressGroup: InstanceType<PressGroup>,
              readonly clientType: string = CLIENT_TYPE.NORMAL) {

  }

  private async refundFangKa() {
    const {player, pressGroup} = this

    const re = await request.post(pcUrl + `/provideForOtherGame/refund`)
      .send({
        gem: pressGroup.priceInFangka,
        wechatUnionid: player.wechatUnionid,
      })
      .then(r => {
        if (r && r.body && r.body.ok) {
          return r.body
        } else {
          return {ok: false}
        }
      }).catch(e => {
        console.error(pcUrl + '/provideForOtherGame/refund ERROR', e)
        return {ok: false}
      })
  }

  private async reducePressGroupIncome(deltaIncome) {
    const {pressGroup} = this

    await PressGroupIncomeModel.update({
      pressGroup: pressGroup._id,
    }, {
      $inc: {income: -deltaIncome},
    }, {upsert: true, new: true})
  }

  private async addBackPressGroupIncome(deltaIncome) {
    const {pressGroup} = this

    await PressGroupIncomeModel.update({
      pressGroup: pressGroup._id,
    }, {
      $inc: {income: deltaIncome},
    }, {upsert: true, new: true})
  }

  async start() {

    const {player, pressGroup, clientType} = this

    return withLock(this.player.id, async () => {

      return await withLock(pressGroup.id, async () => {
        // if (!await detectStockOfPress(pressGroup.id))
        //   throw InternalError("NO_ON_STOCK")
        const price = clientType === CLIENT_TYPE.NORMAL ?
          pressGroup.priceInCoin : clientType === CLIENT_TYPE.PUCHENG ?
            pressGroup.priceInFangka : 9999999

        const {ok, consume} = await playerUseCoinOrGemOrPoint(
          this.player, price, pressGroup.id, 'pressAccurate')
        if (!ok)
          throw InternalError('NO_ENOUGH_COIN')

        const {ok: okay, data} = await getProductsFromPressGroupLevels(pressGroup._id)
        if (!okay) {
          await ConsumeRecordModel.findByIdAndUpdate(consume, {$set: {success: false}})
          if (clientType === CLIENT_TYPE.NORMAL)
            await PlayerModel.findByIdAndUpdate(player.id, {$inc: {coin: consume.coin, freeCoin: consume.freeCoin}})
          else if (clientType === CLIENT_TYPE.PUCHENG)
            await this.refundFangKa()
          await PressGroupModel.findByIdAndUpdate(pressGroup.id, {$set: {onStock: false}})
          throw InternalError(data.message || "NO_ENOUGH_STOCK")
        }
        const preProducts = data.result

        const income = clientType === CLIENT_TYPE.NORMAL ?
          consume.coin : clientType === CLIENT_TYPE.PUCHENG ?
            consume.fangKa * FANGKA_2_COIN : 0
        const pressGroupIncome = await PressGroupIncomeModel.findOneAndUpdate({
          pressGroup: pressGroup._id,
        }, {
          $inc: {income},
          $set: {updateAt: new Date()}
        }, {upsert: true, new: true})

        await PressGroupModel.findByIdAndUpdate(pressGroup._id, {$inc: {hotNum: 1}})

        let win = 0
        const threshold = await pressGroupIncomeThreshold(pressGroup)
        for (let i = 3; i > 0; i--) {
          if (pressGroupIncome.income >= threshold[`income${i}`]) {
            win = i
            break
          }
        }

        const winLevel = win > 2 ? (Math.random() > 0.5 ? 3 : 2) : win
        if (winLevel > 0)
          await this.reducePressGroupIncome(threshold[`income${winLevel}`])

        const activity = await GameActivityModel.create({
          player: player.id,
          pressGroup: pressGroup.id,
          winLevel,
          preProducts,
          feeInFen: pressGroup.priceInCoin * 100,
          endAt: new Date(Date.now() + ms(`${PRESS_GAME_RESUME_TIME}s`)),
          from: `pressAccurate`,
          income: threshold[`income${winLevel}`]
        })
        return activity
      })
    })
  }

  async claim(activity: InstanceType<GameActivity>, winLevel: number):
    Promise<{
      ok: boolean, data:
        {
          winLevel: number,
          gamePrize: InstanceType<GamePrize>,
          message: ErrorCode
        }
    }> {

    try {
      return await withLock(this.player.id, async () => {

        const {pressGroup, player} = this

        const act = await GameActivityModel.findById(activity.id)

        if (act.state === 'done')
          throw InternalError('ALREADY_CLAIMED')

        const preProducts: PreProducts = act.preProducts

        let p = null
        let lipstickseries = null

        let prize = null
        if (winLevel > 0 && (winLevel === 1 || winLevel <= activity.winLevel)) {
          p = await ProductModel.findById(preProducts[`level${winLevel}`]._id)
          if (!p)
            throw InternalError("NO_SUCH_PRODUCT")

          lipstickseries = await LipstickSeriesModel.findById(preProducts[`level${winLevel}`].seriesId)
          if (!lipstickseries)
            throw InternalError("NO_SUCH_SERIES")

          delete preProducts[`level${winLevel}`]
          prize = await GamePrizeModel.create({
            player: player._id,
            lipstickSeries: lipstickseries._id,
            pressGroup: pressGroup._id,
            product: p._id,
            productName: p.name,
            productModel: p.model,
            productDescription: p.description,
            productUrl: p.coverUrl,
            inviteBy: player.inviteBy,
            state: 'idle',
            createAt: new Date(),
            from: 'pressAccurate',
            selectState: `waitSelect`,
            remarks: winLevel.toString()
          })

          for (const it in preProducts) {
            if (preProducts[it])
              await ProductModel.findByIdAndUpdate(preProducts[it]._id, {$inc: {stock: 1}})
          }

          const broadcastInfo = {
            name: player.name,
            from: '按的准',
            info: `获得${4 - winLevel}等奖，奖品为：${prize.productName}`,
            createAt: new Date(Date.now())
          }
          putDataInBroadcastInfo(broadcastInfo)

          await MailModel.create({
            title: `按的准游戏通知`,
            content: `恭喜您获得${4 - winLevel}等奖，${prize.productName}已经添加到您的奖品列表中`,
            to: prize.player,
            type: MailType.NOTICE,
            gift: {prize: prize._id},
            giftState: GiftState.NOTALLOW,
            state: MailState.UNREAD
          })
        }

        activity.state = 'done'
        activity.realWinLevel = winLevel
        await activity.save()
        if (winLevel > 0)
          await this.reducePressGroupIncome(lipstickseries.seriesPrice * PRICE_2_INCOME)

        if (winLevel === 0) {
          const broadcastInfo = {
            name: player.name,
            from: '按的准',
            info: `未获奖`,
            createAt: new Date(Date.now())
          }
          putDataInBroadcastInfo(broadcastInfo)
        }

        if (activity.winLevel > 0)
          await this.addBackPressGroupIncome(activity.income)

        const pressGroupNow1 = await PressGroupModel.findById(pressGroup.id)
          .populate(`level1`)
          .populate(`level2`)
          .populate(`level3`)
        for (let i = 0; i < 3; i++) {
          let onStock = false
          for (const it of (pressGroupNow1[`level${i + 1}`] as Array<InstanceType<LipstickSeries>>)) {
            const haveStock = await detectStockOfLips(it.id)
            if (haveStock) {
              onStock = true
              break
            }
          }
          if (!onStock)
            await PressGroupModel.findByIdAndUpdate(pressGroup._id, {$set: {state: `off`}})
        }

        return {ok: true, data: {winLevel, gamePrize: prize}}
      })
    } catch (e) {
      return {
        ok: false,
        data: {
          winLevel: null, gamePrize: null,
          message: e.message
        }
      }
    }

  }
}

export async function detectStockOfPress(id: Types.ObjectId): Promise<boolean> {
  const pressGroup = await PressGroupModel.findById(id)
    .populate(`level1`)
    .populate(`level2`)
    .populate(`level3`)

  if (!pressGroup ||
    pressGroup.level1.length === 0 ||
    pressGroup.level2.length === 0 ||
    pressGroup.level3.length === 0)
    return false

  const result = {
    level1: null,
    level2: null,
    level3: null,
  }
  const nums = [1, 2, 3]
  for (const num of nums) {
    const series = {_id: null, seriesId: null}
    let stock = 0
    for (const it of pressGroup[`level${num}`]) {
      const s = await LipstickSeriesModel.findById(it._id).lean()
      if (!s || !s.onStock) continue

      const ps = await ProductModel.find({
          _id: {$in: s.products},
          state: {$in: [`on`, `limited`, `preview`]},
          stock: {$gt: 0}
        }
      ).sort({stock: -1}).lean()
      if (ps.length > 0 && ps[0]._id && ps[0].stock > stock) {
        stock = ps[0].stock
        series._id = ps[0]._id
        series.seriesId = s._id
      }
    }
    if (series._id)
      result[`level${num}`] = series
  }
  if (!result.level1 || !result.level2 || !result.level3) {
    await PressGroupModel.findByIdAndUpdate(id, {$set: {state: `off`}})
    return false
  }
  return true
}
